package cc.mrbird.febs.common.authentication;

import cc.mrbird.febs.common.utils.MD5Util;
import cc.mrbird.febs.system.entity.Menu;
import cc.mrbird.febs.system.entity.Role;
import cc.mrbird.febs.system.entity.User;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.ldap.UnsupportedAuthenticationMechanismException;
import org.apache.shiro.realm.ldap.AbstractLdapRealm;
import org.apache.shiro.realm.ldap.JndiLdapContextFactory;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.AuthenticationNotSupportedException;
import org.springframework.stereotype.Component;

import cc.mrbird.febs.system.service.IMenuService;
import cc.mrbird.febs.system.service.IRoleService;
import cc.mrbird.febs.system.service.IUserService;

@Component
public class LdapRealm extends AbstractLdapRealm{
	
    @Autowired
    private IUserService userService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IMenuService menuService;

	private LdapContextFactory ldapContextFactory = null;
	private static final Logger log = LoggerFactory.getLogger(AbstractLdapRealm.class);


	private LdapContextFactory ensureContextFactory() {
        if (this.ldapContextFactory == null) {
 
            if (log.isDebugEnabled()) {
                log.debug("No LdapContextFactory specified - creating a default instance.");
            }
 
            JndiLdapContextFactory defaultFactory = new JndiLdapContextFactory();
            defaultFactory.setUrl(this.url);
            defaultFactory.setSystemUsername(this.systemUsername);
            defaultFactory.setSystemPassword(this.systemPassword);
 
            this.ldapContextFactory = defaultFactory;
        }
        return this.ldapContextFactory;
    }

	
    private String rootDN;

    public String getRootDN() {
        return rootDN;
    }

    public void setRootDN(String rootDN) {
        this.rootDN = rootDN;
    }
    
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	
		AuthenticationInfo info;
        try { 	
            info = queryForAuthenticationInfo(token, ensureContextFactory()); 
        } catch (AuthenticationNotSupportedException e) {
            String msg = "Unsupported configured authentication mechanism";
            throw new UnsupportedAuthenticationMechanismException(msg, e);
        } catch (javax.naming.AuthenticationException e) {
            String msg = "LDAP authentication failed.";
            throw new AuthenticationException(msg, e);
        } catch (NamingException e) {
            String msg = "LDAP naming error while attempting to authenticate user.";
            throw new AuthenticationException(msg, e);
        } catch (UnknownAccountException e) {
            String msg = "账号不存在!";
            throw new UnknownAccountException(msg, e);
        } catch (IncorrectCredentialsException e) {
            String msg = "IncorrectCredentialsException";
            throw new IncorrectCredentialsException(msg, e);
        }

        return info;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		User user = (User) SecurityUtils.getSubject().getPrincipal();
        String userName = user.getUsername();

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 获取用户角色集
        List<Role> roleList = this.roleService.findUserRole(userName);
        Set<String> roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());
        simpleAuthorizationInfo.setRoles(roleSet);

        // 获取用户权限集
        List<Menu> permissionList = this.menuService.findUserPermissions(userName);
        Set<String> permissionSet = permissionList.stream().map(Menu::getPerms).collect(Collectors.toSet());
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
	}

	@Override
	protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
			LdapContextFactory ldapContextFactory) throws NamingException {
		Object principal = token.getPrincipal();//输入的用户名
        Object credentials = token.getCredentials();//输入的密码
        String userName = principal.toString();
        
        
        String password = new String((char[]) credentials);
        User user=null;
        LdapContext systemCtx = null;
        LdapContext ctx = null;
        try {
            //使用系统配置的用户连接LDAP
            systemCtx = ldapContextFactory.getSystemLdapContext();

            SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);//搜索范围是包括子树

            NamingEnumeration<SearchResult> results = systemCtx.search(rootDN, "cn=" + principal , constraints);
            if (results != null && !results.hasMore()) {
                throw new UnknownAccountException();
            } else {
                while (results.hasMore()) {
                    SearchResult si = (SearchResult) results.next();
                    principal = si.getName() + "," + rootDN;

                }

                try {
                    //根据查询到的用户与输入的密码连接LDAP，用户密码正确才能连接
                    ctx = ldapContextFactory.getLdapContext(principal, credentials);
                    //通过用户名到数据库查询用户信息
                    user=dealUser(userName, password,principal);
                } catch (NamingException e) {
                    throw new IncorrectCredentialsException();
                }
                return new SimpleAuthenticationInfo(user, password, getName());
            }
        } finally {
            //关闭连接
            LdapUtils.closeContext(systemCtx);
            LdapUtils.closeContext(ctx);
        }
	}
	
	private User dealUser(String userName, String password,Object principal) {
        if (StringUtils.isEmpty(userName)) {
            return null;
        }
        //TO DO...
     // 通过用户名到数据库查询用户信息
        User user = this.userService.findByName(userName);
        
        if (user == null)
        {    
        	//TODO:新增用户，通过默认域设置权限
        	//从ldap端获取信息
			//String DeptOU=	principal.toString().split("ou=")[1].split(",")[0];
			//Dept dept=deptService.getDeptsIdByOU(DeptOU);
			//if(dept!=null)
			//{
			//   user.setDeptId(dept.getDeptId());
			//   user.setDeptName(dept.getDeptName());
			//}
        	
        	throw new LockedAccountException("HR账号未导入本地系统,请联系管理员！");  
            //userService.regist(userName, password);  
            //user = this.userService.findByName(userName);
        }
        else
        {
        	  if (!StringUtils.equals(MD5Util.encrypt(userName.toLowerCase(), password), user.getPassword()))
                  throw new IncorrectCredentialsException("用户名或密码错误！");
              if (User.STATUS_LOCK.equals(user.getStatus()))
                  throw new LockedAccountException("账号已被锁定,请联系管理员！");  
        }
      
        return user;
    }

	
    public void clearCache() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

	@Override
	protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal,
			LdapContextFactory ldapContextFactory) throws NamingException {
		// TODO Auto-generated method stub
		return null;
	}


}